import os
import sys
import ConfigParser
import threading
import signal
import logging
import fcntl
import errno
from xml.dom import DOMException
import xml.dom.minidom as xml
# Atmos modules
sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), "../../lib"))
import common
# This is the mdssync exit code definition based on standard Linux exit code
# In this version mdssync, we just define the exit code in function do_precheck
# TODO define the whole script exit code
# NOTE exit code related to dryrun is used by DEV,
# Before update exit code, please contact them.
SUCCESS    = 0
ECOMMON    = 1
EUNMOUNED  = 6
ENPEER     = 11
ENSPCTS    = 28
ENSPCSS    = 105

ELOG       = 30
ELOGRO     = 31

EFILE      = 40
ESPLIT     = 41
ETRANS     = 42
EMERGE     = 43


ERECOVER   = 70

ESYSCHECK  = 80

LOG_ID = os.path.basename(sys.argv[0])+'full'
log = logging.getLogger(LOG_ID)

class Watcher:
    """this class solves two problems with multithreaded
    programs in Python, (1) a signal might be delivered
    to any thread (which is just a malfeature) and (2) if
    the thread that gets the signal is waiting, the signal
    is ignored (which is a bug).
    """

    def __init__(self):
        """ Creates a child process, which returns.  The parent
            process waits for a KeyboardInterrupt and then kills
            the child process.
        """
        self.child = os.fork()
        if self.child == 0:
            return
        else:
            self.watch()

    def watch(self):
        try:
            _, ret = os.wait()
            ret = ret >> 8
        except KeyboardInterrupt:
            self.kill()
            ret = 1

        sys.exit(ret)

    def kill(self):
        try:
            os.kill(self.child, signal.SIGKILL)
        except OSError: 
            pass


class Properties(common.Properties):
    """Parse xml from string instead of mds_cfg.xml file in common.Properties"""

    def __init__(self, prop_str):
        """
        Create properties from string

        @type prop_str: str
        @param prop_str: The properties xml string
        """
        try:
            self.props = xml.parseString(prop_str)
        except IOError:
            raise
        except DOMException:
            raise


class MdssyncException(Exception):
    """
    MdssyncException consist of errcode and errstr
    """
    def __init__(self, errcode, errstr):
        """
        init 
        """
        self.errcode = errcode
        self.errstr = errstr


class MdssyncStatus(object):#TODO refine this class to (status, message).
    """
    MdssyncStatus
    """
    NO_START     = 0
    RUNNING      = 1
    SUCCESS_END  = 2
    ERR          = 3
    def __init__(self, status = 0, errcode = SUCCESS, errmessage = ""):
        """
        init
        """
        self.status = status
        self.errcode = errcode
        self.errmessage = errmessage


class MdssyncThread(threading.Thread):
    """
    Base thread model
    """
    def __init__(self):
        super(MdssyncThread, self).__init__()
        self.threadstatus = MdssyncStatus()
        self.mutux = threading.Lock()
        self.setDaemon(True)
        self.stop_event = threading.Event()

    def start(self):
        """
        start the thread
        """
        self.set_thread_status(status = MdssyncStatus.RUNNING)
        super(MdssyncThread, self).start()

    def stop(self):
        """
        stop the thread
        """
        self.stop_event.set()

    def get_thread_status(self):
        """
        return thread status
        """
        return self.threadstatus

    def set_thread_status(self, status = None, errcode = None, errmessage = None):
        """
        set thread status
        """
        self.mutux.acquire()
        if status != None:
            self.threadstatus.status = status
        if errcode != None:
            self.threadstatus.errcode = errcode
        if errmessage != None:
            self.threadstatus.errmessage = errmessage
        self.mutux.release()


class Status_Mgr:
    """
    Class to check and control the tool running states.
    """
    EMPTY = "empty"#TODO redefine variable
    DONE = "done"
    NO_START = "no_start"
    NO_KEY = None


    def __init__(self, status_list, stat_file):
        self.stat_file = stat_file
        self.stat_mgr = ConfigParser.ConfigParser()
        self.mutux = threading.Lock()
        if not os.path.exists(self.stat_file):
            #Init the status file, and set default value
            self.stat_mgr.add_section("Status")
            for key in status_list:
                self.stat_mgr.set("Status", key, self.NO_START)
            self.stat_mgr.write(open(self.stat_file, 'w'))
        else:
            self.stat_mgr.read(stat_file)

    # Read status form status file
    def get_status(self, key):
        try:
            value = self.stat_mgr.get('Status', key)
            return value.strip()
        except Exception, msg:
            log.debug("[Status_Mgr] Failed to get status for key %s", key)
            log.debug("[Status_Mgr] Error message: %s", msg)
            return self.NO_KEY

    # Set status to status file
    def set_status(self, key, value):
        try:
            self.mutux.acquire()
            self.stat_mgr.set("Status", key, value)
            self.stat_mgr.write(open(self.stat_file,'r+'))
            self.mutux.release()
            return True
        except Exception, msg:
            log.debug("[Status_Mgr] Failed to set status for key %s ", key)
            log.debug("[Status_Mgr] Error message: %s", msg)
            self.mutux.release()
            return False

# copied from tendo library http://pypi.python.org/pypi/tendo
# LICENSE: PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
# https://github.com/pycontribs/tendo/blob/master/tendo/singleton.py
class SingleInstance:
    """
    If you want to prevent your script from running in parallel just instantiate SingleInstance() class. If is there another instance already running it will exist the application with the message "Another instance is already running, quitting.", returning -1 error code.

    >>> import tendo
    ... me = SingleInstance()

    This option is very useful if you have scripts executed by crontab at small amounts of time.

    Remember that this works by creating a lock file with a filename based on the full path to the script file.
    """
    LOCK_FILE_LOCATION = "/var/lock/"

    def __init__(self, program_name, instance=None):
        self.initialized = False
        if instance != None:
            lockfile_basename = "lock-%s"% instance
        else:
            lockfile_basename = "lock"
        lockdir = self.LOCK_FILE_LOCATION + program_name + '/'
        if not os.path.exists(lockdir):
            os.mkdir(lockdir)
        elif os.path.isfile(lockdir):
            log.error("Lock directory %s exists, but not a file", lockdir)
            raise Exception("Lock directory %s exists, but not a file", lockdir)

        # os.path.splitext(os.path.abspath(sys.modules['__main__'].__file__))[0].replace("/", "-").replace(":", "").replace("\\", "-") + '-%s' % flavor_id + '.lock'
        self.lockfile = os.path.join(lockdir, lockfile_basename)

        log.debug("SingleInstance lockfile: " + self.lockfile)
        if sys.platform == 'win32':
            try:
                # file already exists, we try to remove (in case previous execution was interrupted)
                if os.path.exists(self.lockfile):
                    os.unlink(self.lockfile)
                self.fd = os.open(self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
            except OSError:
                _, e, _ = sys.exc_info()
                if e.errno == 13:
                    log.error("Another instance is already running, quitting.")
                    sys.exit(errno.EALREADY)
                log.error(e.errno)
                raise
        else:  # non Windows

            self.fp = open(self.lockfile, 'w')
            try:
                fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
            except IOError:
                log.warning("Another instance is already running, quitting.")
                sys.exit(errno.EALREADY)
        self.initialized = True

    def __del__(self):
        if not self.initialized:
            return
        try:
            if sys.platform == 'win32':
                if hasattr(self, 'fd'):
                    os.close(self.fd)
                    os.unlink(self.lockfile)
            else:
                fcntl.lockf(self.fp, fcntl.LOCK_UN)
                #os.close(self.fp)
                if os.path.isfile(self.lockfile):
                    os.unlink(self.lockfile)
        except Exception, e:
            log.warning(e)
            sys.exit(ECOMMON)